////////////////////////////////////////////////////////////////////////////////
//
//  MICROSOFT CORPORATION
//  Copyright (c) Microsoft Corporation.
//  All Rights Reserved.
//
//  NOTICE: Microsoft Confidential. Intended for Internal Use Only.
//
////////////////////////////////////////////////////////////////////////////////

package com.msn.beet.tracking.atlas
{

import com.msn.beet.logging.Log;
import com.msn.beet.tracking.ISender;
import com.msn.beet.tracking.ITrackAction;
import com.msn.beet.tracking.ITrackModel;

import flash.display.Loader;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.TimerEvent;
import flash.net.URLRequest;
import flash.system.Capabilities;
import flash.utils.Timer;

/**
 * Standalone class for sending strings to the Atlas server. Appends
 * parameters to the base url, delimited by '/'. Name value pairs are
 * concatenated by '.'.
 *
 */
public class AtlasSender implements ISender
{
    include "../../core/Version.as";

    protected static const CALL_INTERVAL:int = 2000;

    // TODO: Methinks this class is rather incohesive
    protected var atlasUrl:String;
    protected var atlasEventType:String;
    protected var atlasVersion:String;
    protected var country:String;
    protected var baseActionTag:String;
    protected var language:String;
    protected var screenX:int;
    protected var screenY:int;
    protected var loader:Loader = new Loader();
    protected var queue:Array /* of String */ = [];
    protected var isLoading:Boolean = false;
    protected var timer:Timer;

    protected var _model:AtlasModel = new AtlasModel();

    /**
     * Constructor creates base string, sets up loader for querying atlas
     * server, sets up queue for tracks, initializes default AtlasModel for
     * accessing section and location values, requires external config for
     * atlas server settings.
     *
     * @param config
     * @see AtlasModel
     */
    public function AtlasSender(config:XML)
    {
        loadSettings(config);

        loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
                                                  trackCompleteHandler);
        loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR,
                                                  trackErrorHandler);

        timer = new Timer(CALL_INTERVAL, 1);
        timer.addEventListener(TimerEvent.TIMER_COMPLETE, trackTimeoutHandler);

        _model.baseString = buildBaseString();
    }

    /**
    * Sets AtlasSender to reference an external model.
    */
    public function set model(value:ITrackModel):void
    {
        _model = value as AtlasModel;
        _model.baseString = buildBaseString();
    }

    /**
    * Calls strategy in IAtlasTrackAction.
    */
    public function send(trackAction:ITrackAction):void
    {
        if (!(trackAction is IAtlasTrackAction))
        {
            return;
        }

        var str:String = IAtlasTrackAction(trackAction).parseForAtlas(_model);

        if (str != null)
        {
            enqueueTrack(str);
        }
    }


    /**
    * Load Configuration XML for atlas server settings.
    */
    protected function loadSettings(config:XML):void
    {
        if (config.name() != "atlasConfig")
        {
            var error:ArgumentError = new ArgumentError(
                "Incorrect xml config passed to AtlasSender/loadSettings()");
            Log.fatal(error);
            throw error;
        }

        atlasUrl       = config.atlasUrl;
        atlasEventType = config.atlasEventType;
        atlasVersion   = config.atlasVersion;
        country        = config.country;
        baseActionTag  = config.baseActionTag;

        // get native settings
        language = Capabilities.language;
        screenX = Capabilities.screenResolutionX;
        screenY = Capabilities.screenResolutionY;
    }

    /**
    * Construct base string, similar to:
    * http://switch.atdmt.com/action/riax_test/v3/language.en/country.us/re\
    * solution.1280x1024/
    */
    protected function buildBaseString():String
    {
        var baseString:String = "http://" + atlasUrl + "/" +
            join(atlasEventType) +
            join(baseActionTag) +
            join(atlasVersion) +
            join(language, "language") +
            join(country, "country") +
            join(screenX +"x"+ screenY, "resolution");
        return baseString;
    }


    /**
     * Formats single and pair values for atlas ("tag.value/" or "value/"),
     * omits tags where value = "", will throw reference error if value is
     * null.
     * @param tag Atlas keyword for value, pass "" if
     * @param value
     *
     */
    protected function join(value:String, tag:String = ""):String
    {
        if (value == "")
        {
            return "";
        }
        else if (value == null)
        {
            throw new ReferenceError("Attempted access of " +
                                     "unitialized value.")
        }

        if (tag != "")
        {
            tag += ".";
        }

        return tag + cleanse(value) + "/";
    }

    /**
    * Replaces selected \?&.# with _.
    */
    protected function cleanse(trackString:String):String
    {
        return trackString.replace(/\/|\?|&|\.|#/g, "_");
    }

    // *** QUEUE Methods ***

    /**
    * Adds tracking string to queue, starts queue if idle.
    */
    protected function enqueueTrack(trackString:String):void
    {
        queue.unshift(trackString);

        if (!isLoading)
        {
            sendQueueItem();
        }
    }

    private function sendQueueItem():void
    {
        isLoading = true;
        loader.load(new URLRequest(queue.pop()));
        timer.start();
    }

    /**
    * Fired on a succesful tracking call, sends next track.
    */
    private function trackCompleteHandler(e:Event):void
    {
        timer.reset();
        isLoading = false;

        if (queue.length > 0)
        {
            sendQueueItem();
        }
    }

    /**
    * Fired when a tracking call times out, sends next tracking call.
    */
    private function trackTimeoutHandler(e:TimerEvent):void
    {
        try
        {
            loader.close();
        }
        catch(e:*)
        {
            // TODO: DO NOT DO THIS. Find a better way to handle the
            //       specific exception.
        }

        isLoading = false;
        timer.reset();

        if (queue.length > 0)
        {
            sendQueueItem();
        }
    }

    private function trackErrorHandler(e:IOErrorEvent):void
    {
        // TODO: DO NOT DO THIS. Find a better way to handle the
        //       specific exception.
    }

}

}
